﻿using CluelessServer;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms;

namespace CluelessServer
{
    public class Network
    {
        public event EventHandler<DebugMessageEventArgs> DebugMessage;

        public event EventHandler<UserChangeEventArgs> UserConnected;
        public event EventHandler<UserChangeEventArgs> UserDisconnected;
        public event EventHandler<CharacterSelectedEventArgs> CharacterSelected;
        public event EventHandler<EndTurnEventArgs> EndTurn;
        public event EventHandler<CharacterMovedEventArgs> CharacterMoved;
        public event EventHandler<CharacterMovedEventArgs> CharacterForcedMoved;
        public event EventHandler<SuggestionEventArgs> Suggestion;
        public event EventHandler<SuggestionEventArgs> Disproved;
        public event EventHandler<SuggestionEventArgs> Accusation;
        public event EventHandler<SuggestionEventArgs> PlayerLost;


        //The ClientInfo structure holds the required information about every
        //client connected to the server
        /*struct ClientInfo
        {
            public Socket socket;   //Socket of the client
            public string strName;  //Name by which the user logged into the chat room
        }*/

        //The collection of all clients logged into the room (an array of type ClientInfo)
        ArrayList clientList;

        //The main socket on which the server listens to the clients
        Socket serverSocket;

        byte[] byteData = new byte[1024];

        public Network()
        {
            clientList = new ArrayList();

            try
            {
                //We are using TCP sockets
                serverSocket = new Socket(AddressFamily.InterNetwork,
                                          SocketType.Stream,
                                          ProtocolType.Tcp);

                //Assign the any IP of the machine and listen on port number 1000
                IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse("192.168.2.2"), 1000);//IPAddress.Any, 1000);

                //Bind and listen on the given address
                serverSocket.Bind(ipEndPoint);
                serverSocket.Listen(4);

                //Accept the incoming clients
                serverSocket.BeginAccept(new AsyncCallback(OnAccept), null);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "SGSserverTCP",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void SendDebugMessage(String s)
        {
            DebugMessageEventArgs args = new DebugMessageEventArgs();
            args.message = s;
            args.messageType = MessageType.Null;
            OnDebugMessage(args);
        }

        public ClientInfo Servers
        {
            get
            {
                throw new System.NotImplementedException();
            }
            set
            {
            }
        }

        private void OnAccept(IAsyncResult ar)
        {
            try
            {
                Socket clientSocket = serverSocket.EndAccept(ar);

                //Start listening for more clients
                serverSocket.BeginAccept(new AsyncCallback(OnAccept), null);

                //Once the client connects then start receiving the commands from her
                clientSocket.BeginReceive(byteData, 0, byteData.Length, SocketFlags.None,
                    new AsyncCallback(OnReceive), clientSocket);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "SGSserverTCP",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        protected virtual void OnDebugMessage(DebugMessageEventArgs e)
        {
            EventHandler<DebugMessageEventArgs> handler = DebugMessage;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        private void OnReceive(IAsyncResult ar)
        {
            try
            {

                Socket clientSocket = (Socket)ar.AsyncState;
                clientSocket.EndReceive(ar);

                //Transform the array of bytes received from the user into an
                //intelligent form of object Data
                Data msgReceived = new Data(byteData);

                //We will send this object in response the users request
                Data msgToSend = new Data();

                byte[] message;

                //If the message is to login, logout, or simple text message
                //then when send to others the type of the message remains the same
                msgToSend.cmdCommand = msgReceived.cmdCommand;
                msgToSend.strName = msgReceived.strName;

                SendDebugMessage("\nMessage received: " + msgReceived.strMessage + "  ||  " + msgReceived.cmdCommand + "\n");

                switch (msgReceived.cmdCommand)
                {
                    case MessageType.Login:

                        //When a user logs in to the server then we add her to our
                        //list of clients

                        ClientInfo clientInfo = new ClientInfo(clientSocket, msgReceived.strName);

                        clientList.Add(clientInfo);


                        //Set the text of the message that we will broadcast to all usersSendDebugMessage("<< Sending \n");
                        msgToSend.strMessage = "<<<" + msgReceived.strName + " has joined the room>>>";
                        SendUserConnectedMessage(clientInfo);
                        break;

                    case MessageType.Logout:

                        //When a user wants to log out of the server then we search for her 
                        //in the list of clients and close the corresponding connection

                        int nIndex = 0;
                        foreach (ClientInfo client in clientList)
                        {
                            if (client.ServerSocket == clientSocket)
                            {
                                clientList.RemoveAt(nIndex);
                                SendUserDisconnectedMessage(client);
                                break;
                            }
                            ++nIndex;
                            
                        }

                        clientSocket.Close();

                        msgToSend.strMessage = "<<<" + msgReceived.strName + " has left the room>>>";
                        
                        break;

                    case MessageType.Message:

                        //Set the text of the message that we will broadcast to all users
                        msgToSend.strMessage = msgReceived.strName + ": " + msgReceived.strMessage;
                        break;

                    case MessageType.List:

                        //Send the names of all users in the chat room to the new user
                        msgToSend.cmdCommand = MessageType.List;
                        msgToSend.strName = null;
                        msgToSend.strMessage = null;

                        //Collect the names of the user in the chat room
                        foreach (ClientInfo client in clientList)
                        {
                            //To keep things simple we use asterisk as the marker to separate the user names
                            msgToSend.strMessage += client.Name + "*";
                        }

                        message = msgToSend.ToByte();

                        //Send the name of the users in the chat room
                        clientSocket.BeginSend(message, 0, message.Length, SocketFlags.None,
                                new AsyncCallback(OnSend), clientSocket);
                        break;
                    case MessageType.ChooseCharacter:
                        SendCharacterSelectedMessage(msgReceived.strMessage);
                        break;
                    case MessageType.Turn:
                        SendEndTurnMessage(msgReceived.strMessage);
                        break;
                    case MessageType.Move:
                        SendCharacterMovedMessage(msgReceived.strMessage, msgReceived.strName);
                        break;
                    case MessageType.Suggest:
                        SendSuggestionMessage(msgReceived.strMessage, msgReceived.strName);
                        break;
                    case MessageType.SuggestResponse:
                        SendDisproveMessage(msgReceived.strMessage, msgReceived.strName);
                        break;
                    case MessageType.ForceMove:
                        /////////////////////////////////////////////////////////////
                        break;
                    case MessageType.Accuse:
                        SendAccusationMessage(msgReceived.strMessage, msgReceived.strName);
                        break;
                    case MessageType.PlayerLost:
                        SendPlayerLostMessage(msgReceived.strName);
                        break;
                    default:
                        SendDebugMessage("DEFAULT received: " + msgReceived.strMessage + "  ||  " + msgReceived.cmdCommand + "\n");
                        break;
                }

                /****************************************
                 *   IMPORTANT NOTE -- to not fuck up networking
                 *   
                 *      need to add responses to the if below to prevent them from being broadcasted everywhere!
                 * **************************************/
                if (msgToSend.cmdCommand != MessageType.List && msgToSend.cmdCommand != MessageType.PlayerLost  && msgToSend.cmdCommand != MessageType.Accuse && msgToSend.cmdCommand != MessageType.SuggestResponse && msgToSend.cmdCommand != MessageType.Suggest && msgToSend.cmdCommand != MessageType.ChooseCharacter && msgToSend.cmdCommand != MessageType.Turn)   //List messages are not broadcasted
                {
                    message = msgToSend.ToByte();

                    foreach (ClientInfo clientInfo in clientList)
                    {
                        if (clientInfo.ServerSocket != clientSocket ||
                            msgToSend.cmdCommand != MessageType.Login)
                        {
                            //Send the message to all users
                            clientInfo.ServerSocket.BeginSend(message, 0, message.Length, SocketFlags.None,
                                new AsyncCallback(OnSend), clientInfo.ServerSocket);
                            System.Threading.Thread.Sleep(50);
                        }
                    }

                    
                }
                SendDebugMessage("Message to Send: " + msgToSend.strMessage + "\n");
                //If the user is logging out then we need not listen from her
                if (msgReceived.cmdCommand != MessageType.Logout)
                {
                    //Start listening to the message send by the user
                    clientSocket.BeginReceive(byteData, 0, byteData.Length, SocketFlags.None, new AsyncCallback(OnReceive), clientSocket);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "SGSserverTCP", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }



        public void SendMessage(ClientInfo clientInfo, String text, String name, MessageType type)
        {
            SendDebugMessage("Sending Message: " + text + "  | " + type + "\n");

            if (type == MessageType.ChooseCharacter)
            {
                Console.WriteLine("SendChoose");
            }
            Data data = new Data();
            data.cmdCommand = type;
            data.strName = name;
            data.strMessage = text;

            byte[] message = data.ToByte();

            clientInfo.ServerSocket.BeginSend(message, 0, message.Length, SocketFlags.None,
                                new AsyncCallback(OnSend), clientInfo.ServerSocket);
            
        }


        public void OnSend(IAsyncResult ar)
        {
            try
            {

                Socket client = (Socket)ar.AsyncState;
                client.EndSend(ar);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "SGSserverTCP", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }


        public void Connect()
        {
            throw new System.NotImplementedException();
        }

        public void SendHearbeat()
        {
            throw new System.NotImplementedException();
        }

        public void SendMessage()
        {
            throw new System.NotImplementedException();
        }

        /// <summary>
        /// Procs the user connected trigger
        /// </summary>
        /// <param name="name"></param>
        private void SendUserConnectedMessage(ClientInfo client)
        {
            UserChangeEventArgs args = new UserChangeEventArgs();
            args.message = client.Name;
            args.messageType = MessageType.Null;
            args.clientInfo = client;
            OnUserConnectMessage(args);
        }
        private void SendUserDisconnectedMessage(ClientInfo client)
        {
            UserChangeEventArgs args = new UserChangeEventArgs();
            args.message = client.Name;
            args.messageType = MessageType.Null;
            args.clientInfo = client;
            OnUserDisconnectMessage(args);
        }

        /// <summary>
        /// End of turn triggers
        /// </summary>
        /// <param name="client"></param>
        private void SendEndTurnMessage(String message)
        {
            EndTurnEventArgs args = new EndTurnEventArgs();
            args.message = message;
            OnEndTurnMessage(args);
        }
        protected virtual void OnEndTurnMessage(EndTurnEventArgs e)
        {
            EventHandler<EndTurnEventArgs> handler = EndTurn;
            if (handler != null)
            {
                handler(this, e);
            }
        }


        /// <summary>
        /// Trigger the event of a character selected message
        /// </summary>
        /// <param name="character"></param>
        private void SendCharacterSelectedMessage(String character)
        {
            CharacterSelectedEventArgs args = new CharacterSelectedEventArgs();
            args.name = character;
            OnCharacterSelectedMessage(args);
        }

        protected virtual void OnUserConnectMessage(UserChangeEventArgs e)
        {
            EventHandler<UserChangeEventArgs> handler = UserConnected;
            if (handler != null)
            {
                handler(this, e);
            }
        }
        protected virtual void OnUserDisconnectMessage(UserChangeEventArgs e)
        {
            EventHandler<UserChangeEventArgs> handler = UserDisconnected;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// Event Trigger!!  Character has been selected
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnCharacterSelectedMessage(CharacterSelectedEventArgs e)
        {
            EventHandler<CharacterSelectedEventArgs> handler = CharacterSelected;
            if (handler != null)
            {
                handler(this, e);
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="character"></param>
        private void SendCharacterMovedMessage(String location, String character)
        {
            CharacterMovedEventArgs args = new CharacterMovedEventArgs();
            args.location = location;
            args.character = character;
            OnCharacterMovedMessage(args);
        }

        protected virtual void OnCharacterMovedMessage(CharacterMovedEventArgs e)
        {
            EventHandler<CharacterMovedEventArgs> handler = CharacterMoved;
            if (handler != null)
            {
                handler(this, e);
            }
        }



        /// <summary>
        /// 
        /// </summary>
        /// <param name="character"></param>
        private void SendCharacterForceMovedMessage(String location, String character)
        {
            CharacterMovedEventArgs args = new CharacterMovedEventArgs();
            args.location = location;
            args.character = character;
            OnCharacterForcedMovedMessage(args);
        }

        protected virtual void OnCharacterForcedMovedMessage(CharacterMovedEventArgs e)
        {
            EventHandler<CharacterMovedEventArgs> handler = CharacterForcedMoved;
            if (handler != null)
            {
                handler(this, e);
            }
        }



        /// <summary>
        /// 
        /// </summary>
        /// <param name="character"></param>
        private void SendSuggestionMessage(String message, String suggester)
        {
            SuggestionEventArgs args = new SuggestionEventArgs();
            args.message = message;
            args.suggester = suggester;
            OnSuggestionMessage(args);
        }

        protected virtual void OnSuggestionMessage(SuggestionEventArgs e)
        {
            EventHandler<SuggestionEventArgs> handler = Suggestion;
            if (handler != null)
            {
                handler(this, e);
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="character"></param>
        private void SendDisproveMessage(String message, String sender)
        {
            SuggestionEventArgs args = new SuggestionEventArgs();
            args.message = message;
            args.suggester = sender;
            OnDisproveMessage(args);
        }

        protected virtual void OnDisproveMessage(SuggestionEventArgs e)
        {
            EventHandler<SuggestionEventArgs> handler = Disproved;  // was suggestion
            if (handler != null)
            {
                handler(this, e);
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="character"></param>
        private void SendAccusationMessage(String message, String sender)
        {
            SuggestionEventArgs args = new SuggestionEventArgs();
            args.message = message;
            args.suggester = sender;
            OnAccusationMessage(args);
        }

        protected virtual void OnAccusationMessage(SuggestionEventArgs e)
        {
            EventHandler<SuggestionEventArgs> handler = Accusation;  // was suggestion
            if (handler != null)
            {
                handler(this, e);
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="p"></param>
        private void SendPlayerLostMessage(string p)
        {
            SuggestionEventArgs args = new SuggestionEventArgs();
            args.message = p;
            OnPlayerLostMessage(args);
        }
        protected virtual void OnPlayerLostMessage(SuggestionEventArgs e)
        {
            EventHandler<SuggestionEventArgs> handler = PlayerLost;  // was suggestion
            if (handler != null)
            {
                handler(this, e);
            }
        }

    }

    

    /// <summary>
    /// TODO
    /// </summary>
    /// 
    public class CharacterMovedEventArgs : EventArgs
    {
        public String location { get; set; }
        public String character { get; set; }
    }

    public class EndTurnEventArgs : EventArgs
    {
        public String message { get; set; }
    }

    public class CharacterSelectedEventArgs : EventArgs
    {
        public String name { get; set; }
    }

    public class UserChangeEventArgs : EventArgs
    {
        public MessageType messageType { get; set; }
        public String message { get; set; }
        public ClientInfo clientInfo { get; set; }
    }

    public class DebugMessageEventArgs : EventArgs
    {
        public MessageType messageType { get; set; }
        public String message { get; set; }
    }

    public class SuggestionEventArgs : EventArgs
    {
        public String message { get; set; }
        public String suggester { get; set; }
    }
}
